
// Compiler implementation of the D programming language
// Copyright (c) 1999-2008 by Digital Mars
// All Rights Reserved
// written by Walter Bright
// http://www.digitalmars.com
// License for redistribution is by either the Artistic License
// in artistic.txt, or the GNU General Public License in gnu.txt.
// See the included readme.txt for details.

// This implements the Ddoc capability.

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <assert.h>

#include "../l_service/c_ds/rmem.h"
#include "../l_service/c_ds/root.h"
#include "../l_neo_env/global.h"
#include "../h_nenv.h"

#include "mars.h"
#include "dsymbol.h"
#include "macro.h"
#include "template.h"
#include "../l_neo_lexer/lexer.h"
#include "aggregate.h"
#include "declaration.h"
#include "enum.h"
#include "../l_neo_lexer/id.h"
#include "module.h"
#include "scope.h"
#include "hdrgen.h"
#include "doc.h"
#include "mtype.h"

struct Escape
{
    const char *strings[256];

    static const char *escapeChar(unsigned c);
};

struct Section
{
    unsigned char *name;
    unsigned namelen;

    unsigned char *body;
    unsigned bodylen;

    int nooutput;

    virtual void write(DocComment *dc, Scope *sc, P_Dsymbol *s, OutBuffer *buf);
};

struct ParamSection : Section
{
    void write(DocComment *dc, Scope *sc, P_Dsymbol *s, OutBuffer *buf);
};

struct MacroSection : Section
{
    void write(DocComment *dc, Scope *sc, P_Dsymbol *s, OutBuffer *buf);
};

struct DocComment
{
    Array sections;		// Section*[]

    Section *summary;
    Section *copyright;
    Section *macros;
    Macro **pmacrotable;
    Escape **pescapetable;

    DocComment();

    static DocComment *parse(Scope *sc, P_Dsymbol *s, unsigned char *comment);
    static void parseMacros(Escape **pescapetable, Macro **pmacrotable, unsigned char *m, unsigned mlen);
    static void parseEscapes(Escape **pescapetable, unsigned char *textstart, unsigned textlen);

    void parseSections(unsigned char *comment);
    void writeSections(Scope *sc, P_Dsymbol *s, OutBuffer *buf);
};


int cmp(const char *stringz, void *s, size_t slen);
int icmp(const char *stringz, void *s, size_t slen);
int isDitto(unsigned char *comment);
unsigned char *skipwhitespace(unsigned char *p);
unsigned skiptoident(OutBuffer *buf, unsigned i);
unsigned skippastident(OutBuffer *buf, unsigned i);
unsigned skippastURL(OutBuffer *buf, unsigned i);
void highlightText(Scope *sc, P_Dsymbol *s, OutBuffer *buf, unsigned offset);
void highlightCode(Scope *sc, P_Dsymbol *s, OutBuffer *buf, unsigned offset);
void highlightCode2(Scope *sc, P_Dsymbol *s, OutBuffer *buf, unsigned offset);
Argument *isFunctionParameter(P_Dsymbol *s, unsigned char *p, unsigned len);

static unsigned char ddoc_default[] = "\
DDOC =	<html><head>\n\
	<META http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n\
	<title>$(TITLE)</title>\n\
	</head><body>\n\
	<h1>$(TITLE)</h1>\n\
	$(BODY)\n\
	<hr>$(SMALL Page generated by $(LINK2 http://www.digitalmars.com/d/2.0/ddoc.html, Ddoc). $(COPYRIGHT))\n\
	</body></html>\n\
\n\
B =	<b>$0</b>\n\
I =	<i>$0</i>\n\
U =	<u>$0</u>\n\
P =	<p>$0</p>\n\
DL =	<dl>$0</dl>\n\
DT =	<dt>$0</dt>\n\
DD =	<dd>$0</dd>\n\
TABLE =	<table>$0</table>\n\
TR =	<tr>$0</tr>\n\
TH =	<th>$0</th>\n\
TD =	<td>$0</td>\n\
OL =	<ol>$0</ol>\n\
UL =	<ul>$0</ul>\n\
LI =	<li>$0</li>\n\
BIG =	<big>$0</big>\n\
SMALL =	<small>$0</small>\n\
BR =	<br>\n\
LINK =	<a href=\"$0\">$0</a>\n\
LINK2 =	<a href=\"$1\">$+</a>\n\
\n\
RED =	<font color=red>$0</font>\n\
BLUE =	<font color=blue>$0</font>\n\
GREEN =	<font color=green>$0</font>\n\
YELLOW =<font color=yellow>$0</font>\n\
BLACK =	<font color=black>$0</font>\n\
WHITE =	<font color=white>$0</font>\n\
\n\
D_CODE = <pre class=\"d_code\">$0</pre>\n\
D_COMMENT = $(GREEN $0)\n\
D_STRING  = $(RED $0)\n\
D_KEYWORD = $(BLUE $0)\n\
D_PSYMBOL = $(U $0)\n\
D_PARAM	  = $(I $0)\n\
\n\
DDOC_COMMENT   = <!-- $0 -->\n\
DDOC_DECL      = $(DT $(BIG $0))\n\
DDOC_DECL_DD   = $(DD $0)\n\
DDOC_DITTO     = $(BR)$0\n\
DDOC_SECTIONS  = $0\n\
DDOC_SUMMARY   = $0$(BR)$(BR)\n\
DDOC_DESCRIPTION = $0$(BR)$(BR)\n\
DDOC_AUTHORS   = $(B Authors:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_BUGS      = $(RED BUGS:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_COPYRIGHT = $(B Copyright:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_DATE      = $(B Date:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_DEPRECATED = $(RED Deprecated:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_EXAMPLES  = $(B Examples:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_HISTORY   = $(B History:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_LICENSE   = $(B License:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_RETURNS   = $(B Returns:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_SEE_ALSO  = $(B See Also:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_STANDARDS = $(B Standards:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_THROWS    = $(B Throws:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_VERSION   = $(B Version:)$(BR)\n$0$(BR)$(BR)\n\
DDOC_SECTION_H = $(B $0)$(BR)\n\
DDOC_SECTION   = $0$(BR)$(BR)\n\
DDOC_MEMBERS   = $(DL $0)\n\
DDOC_MODULE_MEMBERS = $(DDOC_MEMBERS $0)\n\
DDOC_CLASS_MEMBERS  = $(DDOC_MEMBERS $0)\n\
DDOC_STRUCT_MEMBERS = $(DDOC_MEMBERS $0)\n\
DDOC_ENUM_MEMBERS   = $(DDOC_MEMBERS $0)\n\
DDOC_TEMPLATE_MEMBERS = $(DDOC_MEMBERS $0)\n\
DDOC_PARAMS    = $(B Params:)$(BR)\n$(TABLE $0)$(BR)\n\
DDOC_PARAM_ROW = $(TR $0)\n\
DDOC_PARAM_ID  = $(TD $0)\n\
DDOC_PARAM_DESC = $(TD $0)\n\
DDOC_BLANKLINE	= $(BR)$(BR)\n\
\n\
DDOC_PSYMBOL	= $(U $0)\n\
DDOC_KEYWORD	= $(B $0)\n\
DDOC_PARAM	= $(I $0)\n\
\n\
ESCAPES = /</&lt;/\n\
	  />/&gt;/\n\
	  /&/&amp;/\n\
";

static char ddoc_decl_s[] = "$(DDOC_DECL ";
static char ddoc_decl_e[] = ")\n";

static char ddoc_decl_dd_s[] = "$(DDOC_DECL_DD ";
static char ddoc_decl_dd_e[] = ")\n";


/****************************************************
 */

void P_Module::gendocfile()
{
    static OutBuffer mbuf;
    static int mbuf_done;

    OutBuffer buf;

    //printf("P_Module::gendocfile()\n");

    if (!mbuf_done)		// if not already read the ddoc files
    {	mbuf_done = 1;

	// Use our internal default
	mbuf.write(ddoc_default, sizeof(ddoc_default) - 1);

	// Override with DDOCFILE specified in the sc.ini file
	char *p = my_getenv("DDOCFILE");
	if (p)
	    global.params.ddocfiles->shift(p);

	// Override with the ddoc macro files from the command line
	for (int i = 0; i < global.params.ddocfiles->dim; i++)
	{
	    FileName f((char *)global.params.ddocfiles->data[i], 0);
	    File file(&f);
	    file.readv();
	    // BUG: convert file contents to UTF-8 before use

	    //printf("file: '%.*s'\n", file.len, file.buffer);
	    mbuf.write(file.buffer, file.len);
	}
    }
    DocComment::parseMacros(&escapetable, &macrotable, mbuf.data, mbuf.offset);

    Scope *sc = Scope::createGlobal(this);	// create root scope
    sc->docbuf = &buf;

    DocComment *dc = DocComment::parse(sc, this, comment);
    dc->pmacrotable = &macrotable;
    dc->pescapetable = &escapetable;

    // Generate predefined macros

    // Set the title to be the name of the module
    {	char *p = toPrettyChars();
	Macro::define(&macrotable, (unsigned char *)"TITLE", 5, (unsigned char *)p, strlen(p));
    }

    time_t t;
    time(&t);
    char *p = ctime(&t);
    p = mem.strdup(p);
    Macro::define(&macrotable, (unsigned char *)"DATETIME", 8, (unsigned char *)p, strlen(p));
    Macro::define(&macrotable, (unsigned char *)"YEAR", 4, (unsigned char *)p + 20, 4);

    char *docfilename = docfile->oToChars();
    Macro::define(&macrotable, (unsigned char *)"DOCFILENAME", 11, (unsigned char *)docfilename, strlen(docfilename));

    if (dc->copyright)
    {
	dc->copyright->nooutput = 1;
	Macro::define(&macrotable, (unsigned char *)"COPYRIGHT", 9, dc->copyright->body, dc->copyright->bodylen);
    }

    buf.printf("$(DDOC_COMMENT Generated by Ddoc from %s)\n", srcfile->oToChars());
    if (isDocFile)
    {
	size_t commentlen = strlen((char *)comment);
	if (dc->macros)
	{
	    commentlen = dc->macros->name - comment;
	    dc->macros->write(dc, sc, this, sc->docbuf);
	}
	sc->docbuf->write(comment, commentlen);
	highlightText(NULL, this, sc->docbuf, 0);
    }
    else
    {
	dc->writeSections(sc, this, sc->docbuf);
	emitMemberComments(sc);
    }

    //printf("BODY= '%.*s'\n", buf.offset, buf.data);
    Macro::define(&macrotable, (unsigned char *)"BODY", 4, buf.data, buf.offset);

    OutBuffer buf2;
    buf2.writestring("$(DDOC)\n");
    unsigned end = buf2.offset;
    macrotable->expand(&buf2, 0, &end, NULL, 0);

#if 1
    /* Remove all the escape sequences from buf2,
     * and make CR-LF the newline.
     */
    {
	buf.setsize(0);
	buf.reserve(buf2.offset);
	unsigned char *p = buf2.data;
	for (unsigned j = 0; j < buf2.offset; j++)
	{
	    unsigned char c = p[j];
	    if (c == 0xFF && j + 1 < buf2.offset)
	    {
		j++;
		continue;
	    }
	    if (c == '\n')
		buf.writeByte('\r');
	    else if (c == '\r')
	    {
		buf.writestring("\r\n");
		if (j + 1 < buf2.offset && p[j + 1] == '\n')
		{
		    j++;
		}
		continue;
	    }
	    buf.writeByte(c);
	}
    }

    // Transfer image to file
    assert(docfile);
    docfile->setbuffer(buf.data, buf.offset);
    docfile->ref = 1;
    char *pt = FileName::path(docfile->oToChars());
    if (*pt)
	FileName::ensurePathExists(pt);
    mem.free(pt);
    docfile->writev();
#else
    /* Remove all the escape sequences from buf2
     */
    {	unsigned i = 0;
	unsigned char *p = buf2.data;
	for (unsigned j = 0; j < buf2.offset; j++)
	{
	    if (p[j] == 0xFF && j + 1 < buf2.offset)
	    {
		j++;
		continue;
	    }
	    p[i] = p[j];
	    i++;
	}
	buf2.setsize(i);
    }

    // Transfer image to file
    docfile->setbuffer(buf2.data, buf2.offset);
    docfile->ref = 1;
    char *pt = FileName::path(docfile->oToChars());
    if (*pt)
	FileName::ensurePathExists(pt);
    mem.free(pt);
    docfile->writev();
#endif
}

/******************************* emitComment **********************************/

/*
 * Emit doc comment to documentation file
 */

void P_Dsymbol::emitDitto(Scope *sc)
{
    //printf("P_Dsymbol::emitDitto() %s %s\n", kind(), oToChars());
    OutBuffer *buf = sc->docbuf;
    unsigned o;
    OutBuffer b;

    b.writestring("$(DDOC_DITTO ");
	o = b.offset;
	toDocBuffer(&b);
	//printf("b: '%.*s'\n", b.offset, b.data);
	/* If 'this' is a function template, then highlightCode() was
	 * already run by P_FuncDeclaration::toDocbuffer().
	 */
	P_TemplateDeclaration *td;
	if (parent &&
	    (td = parent->isTemplateDeclaration()) != NULL &&
	    td->onemember == this)
	{
	}
	else
	    highlightCode(sc, this, &b, o);
    b.writeByte(')');
    buf->spread(sc->lastoffset, b.offset);
    memcpy(buf->data + sc->lastoffset, b.data, b.offset);
    sc->lastoffset += b.offset;
}

void P_ScopeDsymbol::emitMemberComments(Scope *sc)
{
    //printf("P_ScopeDsymbol::emitMemberComments() %s\n", oToChars());
    OutBuffer *buf = sc->docbuf;

    if (members)
    {	const char *m = "$(DDOC_MEMBERS \n";

	if (isModule())
	    m = "$(DDOC_MODULE_MEMBERS \n";
	else if (isClassDeclaration())
	    m = "$(DDOC_CLASS_MEMBERS \n";
	else if (isStructDeclaration())
	    m = "$(DDOC_STRUCT_MEMBERS \n";
	else if (isEnumDeclaration())
	    m = "$(DDOC_ENUM_MEMBERS \n";
	else if (isTemplateDeclaration())
	    m = "$(DDOC_TEMPLATE_MEMBERS \n";

	unsigned offset1 = buf->offset;		// save starting offset
	buf->writestring(m);
	unsigned offset2 = buf->offset;		// to see if we write anything
	sc = sc->push(this);
	for (int i = 0; i < members->dim; i++)
	{
	    P_Dsymbol *s = (P_Dsymbol *)members->data[i];
	    //printf("\ts = '%s'\n", s->oToChars());
	    s->emitComment(sc);
	}
	sc->pop();
	if (buf->offset == offset2)
	{
	    /* Didn't write out any members, so back out last write
	     */
	    buf->offset = offset1;
	}
	else
	    buf->writestring(")\n");
    }
}

void emitProtection(OutBuffer *buf, PROT prot)
{
    const char *p;

    switch (prot)
    {
	case PROTpackage:	p = "package";	 break;
	case PROTprotected:	p = "protected"; break;
	case PROTexport:	p = "export";	 break;
	default:		p = NULL;	 break;
    }
    if (p)
	buf->printf("%s ", p);
}

void P_Dsymbol::emitComment(Scope *sc)		   { }
void P_InvariantDeclaration::emitComment(Scope *sc)  { }
#if V2
void P_PostBlitDeclaration::emitComment(Scope *sc)   { }
#endif
void P_DtorDeclaration::emitComment(Scope *sc)	   { }
void P_StaticCtorDeclaration::emitComment(Scope *sc) { }
void P_StaticDtorDeclaration::emitComment(Scope *sc) { }
void P_ClassInfoDeclaration::emitComment(Scope *sc)  { }
void P_ModuleInfoDeclaration::emitComment(Scope *sc) { }
void P_TypeInfoDeclaration::emitComment(Scope *sc)   { }


void P_Declaration::emitComment(Scope *sc)
{
    //printf("P_Declaration::emitComment(%p '%s'), comment = '%s'\n", this, oToChars(), comment);
    //printf("type = %p\n", type);

    if (protection == PROTprivate || !ident ||
	(!type && !isCtorDeclaration() && !isAliasDeclaration()))
	return;
    if (!comment)
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, comment);
    unsigned o;

    if (!dc)
    {
	emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
	o = buf->offset;
	toDocBuffer(buf);
	highlightCode(sc, this, buf, o);
	sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    buf->writestring(ddoc_decl_dd_e);
}

void P_AggregateDeclaration::emitComment(Scope *sc)
{
    //printf("P_AggregateDeclaration::emitComment() '%s'\n", oToChars());
    if (prot() == PROTprivate)
	return;
    if (!comment)
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, comment);

    if (!dc)
    {
	emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
    toDocBuffer(buf);
    sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    emitMemberComments(sc);
    buf->writestring(ddoc_decl_dd_e);
}

void P_TemplateDeclaration::emitComment(Scope *sc)
{
    //printf("P_TemplateDeclaration::emitComment() '%s', kind = %s\n", oToChars(), kind());
    if (prot() == PROTprivate)
	return;

    unsigned char *com = comment;
    int hasmembers = 1;

    P_Dsymbol *ss = this;

    if (onemember)
    {
	ss = onemember->isAggregateDeclaration();
	if (!ss)
	{
	    ss = onemember->isFuncDeclaration();
	    if (ss)
	    {	hasmembers = 0;
		if (com != ss->comment)
		    com = Lexer::combineComments(com, ss->comment);
	    }
	    else
		ss = this;
	}
    }

    if (!com)
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, com);
    unsigned o;

    if (!dc)
    {
	ss->emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
	o = buf->offset;
	ss->toDocBuffer(buf);
	if (ss == this)
	    highlightCode(sc, this, buf, o);
	sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    if (hasmembers)
	((P_ScopeDsymbol *)ss)->emitMemberComments(sc);
    buf->writestring(ddoc_decl_dd_e);
}

void P_EnumDeclaration::emitComment(Scope *sc)
{
    if (prot() == PROTprivate)
	return;
//    if (!comment)
    {	if (isAnonymous() && members)
	{
	    for (int i = 0; i < members->dim; i++)
	    {
		P_Dsymbol *s = (P_Dsymbol *)members->data[i];
		s->emitComment(sc);
	    }
	    return;
	}
    }
    if (!comment)
	return;
    if (isAnonymous())
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, comment);

    if (!dc)
    {
	emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
	toDocBuffer(buf);
	sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    emitMemberComments(sc);
    buf->writestring(ddoc_decl_dd_e);
}

void P_EnumMember::emitComment(Scope *sc)
{
    //printf("P_EnumMember::emitComment(%p '%s'), comment = '%s'\n", this, oToChars(), comment);
    if (prot() == PROTprivate)
	return;
    if (!comment)
	return;

    OutBuffer *buf = sc->docbuf;
    DocComment *dc = DocComment::parse(sc, this, comment);
    unsigned o;

    if (!dc)
    {
	emitDitto(sc);
	return;
    }
    dc->pmacrotable = &sc->module->macrotable;

    buf->writestring(ddoc_decl_s);
	o = buf->offset;
	toDocBuffer(buf);
	highlightCode(sc, this, buf, o);
	sc->lastoffset = buf->offset;
    buf->writestring(ddoc_decl_e);

    buf->writestring(ddoc_decl_dd_s);
    dc->writeSections(sc, this, buf);
    buf->writestring(ddoc_decl_dd_e);
}

/******************************* toDocBuffer **********************************/

void P_Dsymbol::toDocBuffer(OutBuffer *buf)
{
    //printf("P_Dsymbol::toDocbuffer() %s\n", oToChars());
    HdrGenState hgs;

    hgs.ddoc = 1;
    toCBuffer(buf, &hgs);
}

void prefix(OutBuffer *buf, P_Dsymbol *s)
{
    if (s->isDeprecated())
	buf->writestring("deprecated ");
    P_Declaration *d = s->isDeclaration();
    if (d)
    {
	emitProtection(buf, d->protection);
	if (d->isAbstract())
	    buf->writestring("abstract ");
	if (d->isStatic())
	    buf->writestring("static ");
	if (d->isConst())
	    buf->writestring("const ");
#if V2
	if (d->isInvariant())
	    buf->writestring("invariant ");
#endif
	if (d->isFinal())
	    buf->writestring("final ");
	if (d->isSynchronized())
	    buf->writestring("synchronized ");
    }
}

void P_Declaration::toDocBuffer(OutBuffer *buf)
{
    //printf("P_Declaration::toDocbuffer() %s, originalType = %p\n", oToChars(), originalType);
    if (ident)
    {
	prefix(buf, this);

	if (type)
	{   HdrGenState hgs;
	    hgs.ddoc = 1;
	    if (originalType)
	    {	//originalType->print();
		originalType->toCBuffer(buf, ident, &hgs);
	    }
	    else
		type->toCBuffer(buf, ident, &hgs);
	}
	else
	    buf->writestring(ident->oToChars());
	buf->writestring(";\n");
    }
}


void P_AliasDeclaration::toDocBuffer(OutBuffer *buf)
{
    //printf("P_AliasDeclaration::toDocbuffer() %s\n", oToChars());
    if (ident)
    {
	if (isDeprecated())
	    buf->writestring("deprecated ");

	emitProtection(buf, protection);
	buf->writestring("alias ");
	buf->writestring(oToChars());
	buf->writestring(";\n");
    }
}


void P_TypedefDeclaration::toDocBuffer(OutBuffer *buf)
{
    if (ident)
    {
	if (isDeprecated())
	    buf->writestring("deprecated ");

	emitProtection(buf, protection);
	buf->writestring("typedef ");
	buf->writestring(oToChars());
	buf->writestring(";\n");
    }
}


void P_FuncDeclaration::toDocBuffer(OutBuffer *buf)
{
    //printf("P_FuncDeclaration::toDocbuffer() %s\n", oToChars());
    if (ident)
    {
	P_TemplateDeclaration *td;

	if (parent &&
	    (td = parent->isTemplateDeclaration()) != NULL &&
	    td->onemember == this)
	{   /* It's a function template
	     */
	    HdrGenState hgs;
	    unsigned o = buf->offset;
	    TypeFunction *tf = (TypeFunction *)type;

	    hgs.ddoc = 1;
	    prefix(buf, td);
	    tf->next->toCBuffer(buf, NULL, &hgs);
	    buf->writeByte(' ');
	    buf->writestring(ident->oToChars());
	    buf->writeByte('(');
	    for (int i = 0; i < td->origParameters->dim; i++)
	    {
		TemplateParameter *tp = (TemplateParameter *)td->origParameters->data[i];
		if (i)
		    buf->writestring(", ");
		tp->toCBuffer(buf, &hgs);
	    }
	    buf->writeByte(')');
	    Argument::argsToCBuffer(buf, &hgs, tf->parameters, tf->varargs);
	    buf->writestring(";\n");

	    highlightCode(NULL, this, buf, o);
	}
	else
	{
	    P_Declaration::toDocBuffer(buf);
	}
    }
}

void P_CtorDeclaration::toDocBuffer(OutBuffer *buf)
{
    HdrGenState hgs;

    buf->writestring("this");
    Argument::argsToCBuffer(buf, &hgs, arguments, varargs);
    buf->writestring(";\n");
}


void P_AggregateDeclaration::toDocBuffer(OutBuffer *buf)
{
    if (ident)
    {
#if 0
	emitProtection(buf, protection);
#endif
	buf->printf("%s $(DDOC_PSYMBOL %s)", kind(), oToChars());
	buf->writestring(";\n");
    }
}

void P_StructDeclaration::toDocBuffer(OutBuffer *buf)
{
    //printf("P_StructDeclaration::toDocbuffer() %s\n", oToChars());
    if (ident)
    {
#if 0
	emitProtection(buf, protection);
#endif
	P_TemplateDeclaration *td;

	if (parent &&
	    (td = parent->isTemplateDeclaration()) != NULL &&
	    td->onemember == this)
	{   unsigned o = buf->offset;
	    td->toDocBuffer(buf);
	    highlightCode(NULL, this, buf, o);
	}
	else
	{
	    buf->printf("%s $(DDOC_PSYMBOL %s)", kind(), oToChars());
	}
	buf->writestring(";\n");
    }
}

void P_ClassDeclaration::toDocBuffer(OutBuffer *buf)
{
    //printf("P_ClassDeclaration::toDocbuffer() %s\n", oToChars());
    if (ident)
    {
#if 0
	emitProtection(buf, protection);
#endif
	P_TemplateDeclaration *td;

	if (parent &&
	    (td = parent->isTemplateDeclaration()) != NULL &&
	    td->onemember == this)
	{   unsigned o = buf->offset;
	    td->toDocBuffer(buf);
	    highlightCode(NULL, this, buf, o);
	}
	else
	{
	    if (isAbstract())
		buf->writestring("abstract ");
	    buf->printf("%s $(DDOC_PSYMBOL %s)", kind(), oToChars());
	}
	int any = 0;
	for (int i = 0; i < baseclasses.dim; i++)
	{   BaseClass *bc = (BaseClass *)baseclasses.data[i];

	    if (bc->protection == PROTprivate)
		continue;
	    if (bc->base && bc->base->ident == Id::Object)
		continue;

	    if (any)
		buf->writestring(", ");
	    else
	    {	buf->writestring(": ");
		any = 1;
	    }
	    emitProtection(buf, bc->protection);
	    if (bc->base)
	    {
		buf->writestring(bc->base->toPrettyChars());
	    }
	    else
	    {
		HdrGenState hgs;
		bc->type->toCBuffer(buf, NULL, &hgs);
	    }
	}
	buf->writestring(";\n");
    }
}


void P_EnumDeclaration::toDocBuffer(OutBuffer *buf)
{
    if (ident)
    {
	buf->printf("%s $(DDOC_PSYMBOL %s)", kind(), oToChars());
	buf->writestring(";\n");
    }
}

void P_EnumMember::toDocBuffer(OutBuffer *buf)
{
    if (ident)
    {
	buf->writestring(oToChars());
    }
}


/********************************* DocComment *********************************/

DocComment::DocComment()
{
    memset(this, 0, sizeof(DocComment));
}

DocComment *DocComment::parse(Scope *sc, P_Dsymbol *s, unsigned char *comment)
{   unsigned idlen;

    //printf("parse(%s): '%s'\n", s->oToChars(), comment);
    if (sc->lastdc && isDitto(comment))
	return NULL;

    DocComment *dc = new DocComment();
    if (!comment)
	return dc;

    dc->parseSections(comment);

    for (int i = 0; i < dc->sections.dim; i++)
    {	Section *s = (Section *)dc->sections.data[i];

	if (icmp("copyright", s->name, s->namelen) == 0)
	{
	    dc->copyright = s;
	}
	if (icmp("macros", s->name, s->namelen) == 0)
	{
	    dc->macros = s;
	}
    }

    sc->lastdc = dc;
    return dc;
}

/*****************************************
 * Parse next paragraph out of *pcomment.
 * Update *pcomment to point past paragraph.
 * Returns NULL if no more paragraphs.
 * If paragraph ends in 'identifier:',
 * then (*pcomment)[0 .. idlen] is the identifier.
 */

void DocComment::parseSections(unsigned char *comment)
{   unsigned char *p;
    unsigned char *pstart;
    unsigned char *pend;
    unsigned char *q;
    unsigned char *idstart;
    unsigned idlen;

    unsigned char *name = NULL;
    unsigned namelen = 0;

    //printf("parseSections('%s')\n", comment);
    p = comment;
    while (*p)
    {
	p = skipwhitespace(p);
	pstart = p;

	/* Find end of section, which is ended by one of:
	 *	'identifier:'
	 *	'\0'
	 */
	idlen = 0;
	while (1)
	{
	    if (isalpha(*p) || *p == '_')
	    {
		q = p + 1;
		while (isalnum(*q) || *q == '_')
		    q++;
		if (*q == ':')	// identifier: ends it
		{   idlen = q - p;
		    idstart = p;
		    for (pend = p; pend > pstart; pend--)
		    {	if (pend[-1] == '\n')
			    break;
		    }
		    p = q + 1;
		    break;
		}
	    }
	    while (1)
	    {
		if (!*p)
		{   pend = p;
		    goto L1;
		}
		if (*p == '\n')
		{   p++;
		    if (*p == '\n' && !summary && !namelen)
		    {
			pend = p;
			p++;
			goto L1;
		    }
		    break;
		}
		p++;
	    }
	    p = skipwhitespace(p);
	}
      L1:

	if (namelen || pstart < pend)
	{
	    Section *s;
	    if (icmp("Params", name, namelen) == 0)
		s = new ParamSection();
	    else if (icmp("Macros", name, namelen) == 0)
		s = new MacroSection();
	    else
		s = new Section();
	    s->name = name;
	    s->namelen = namelen;
	    s->body = pstart;
	    s->bodylen = pend - pstart;
	    s->nooutput = 0;

	    //printf("Section: '%.*s' = '%.*s'\n", s->namelen, s->name, s->bodylen, s->body);

	    sections.push(s);

	    if (!summary && !namelen)
		summary = s;
	}

	if (idlen)
	{   name = idstart;
	    namelen = idlen;
	}
	else
	{   name = NULL;
	    namelen = 0;
	    if (!*p)
		break;
	}
    }
}

void DocComment::writeSections(Scope *sc, P_Dsymbol *s, OutBuffer *buf)
{
    //printf("DocComment::writeSections()\n");
    if (sections.dim)
    {
	buf->writestring("$(DDOC_SECTIONS \n");
	for (int i = 0; i < sections.dim; i++)
	{   Section *sec = (Section *)sections.data[i];

	    if (sec->nooutput)
		continue;
	    //printf("Section: '%.*s' = '%.*s'\n", sec->namelen, sec->name, sec->bodylen, sec->body);
	    if (sec->namelen || i)
		sec->write(this, sc, s, buf);
	    else
	    {
		buf->writestring("$(DDOC_SUMMARY ");
		    unsigned o = buf->offset;
		    buf->write(sec->body, sec->bodylen);
		    highlightText(sc, s, buf, o);
		buf->writestring(")\n");
	    }
	}
	buf->writestring(")\n");
    }
    else
    {
	buf->writestring("$(DDOC_BLANKLINE)\n");
    }
}

/***************************************************
 */

void Section::write(DocComment *dc, Scope *sc, P_Dsymbol *s, OutBuffer *buf)
{
    if (namelen)
    {
	static const char *table[] =
	{	"AUTHORS", "BUGS", "COPYRIGHT", "DATE",
		"DEPRECATED", "EXAMPLES", "HISTORY", "LICENSE",
		"RETURNS", "SEE_ALSO", "STANDARDS", "THROWS",
		"VERSION" };

	for (int i = 0; i < sizeof(table) / sizeof(table[0]); i++)
	{
	    if (icmp(table[i], name, namelen) == 0)
	    {
		buf->printf("$(DDOC_%s ", table[i]);
		goto L1;
	    }
	}

	buf->writestring("$(DDOC_SECTION ");
	    // Replace _ characters with spaces
	    buf->writestring("$(DDOC_SECTION_H ");
	    for (unsigned u = 0; u < namelen; u++)
	    {   unsigned char c = name[u];
		buf->writeByte((c == '_') ? ' ' : c);
	    }
	    buf->writestring(":)\n");
    }
    else
    {
	buf->writestring("$(DDOC_DESCRIPTION ");
    }
  L1:
    unsigned o = buf->offset;
    buf->write(body, bodylen);
    highlightText(sc, s, buf, o);
    buf->writestring(")\n");
}

/***************************************************
 */

void ParamSection::write(DocComment *dc, Scope *sc, P_Dsymbol *s, OutBuffer *buf)
{
    unsigned char *p = body;
    unsigned len = bodylen;
    unsigned char *pend = p + len;

    unsigned char *tempstart;
    unsigned templen;

    unsigned char *namestart;
    unsigned namelen = 0;	// !=0 if line continuation

    unsigned char *textstart;
    unsigned textlen;

    unsigned o;
    Argument *arg;

    buf->writestring("$(DDOC_PARAMS \n");
    while (p < pend)
    {
	// Skip to start of macro
	for (; 1; p++)
	{
	    switch (*p)
	    {
		case ' ':
		case '\t':
		    continue;

		case '\n':
		    p++;
		    goto Lcont;

		default:
		    if (!(isalpha(*p) || *p == '_'))
		    {
			if (namelen)
			    goto Ltext;		// continuation of prev macro
			goto Lskipline;
		    }
		    break;
	    }
	    break;
	}
	tempstart = p;

	while (isalnum(*p) || *p == '_')
	    p++;
	templen = p - tempstart;

	while (*p == ' ' || *p == '\t')
	    p++;

	if (*p != '=')
	{   if (namelen)
		goto Ltext;		// continuation of prev macro
	    goto Lskipline;
	}
	p++;

	if (namelen)
	{   // Output existing param

	L1:
	    //printf("param '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart);
	    HdrGenState hgs;
	    buf->writestring("$(DDOC_PARAM_ROW ");
		buf->writestring("$(DDOC_PARAM_ID ");
		    o = buf->offset;
		    arg = isFunctionParameter(s, namestart, namelen);
		    if (arg && arg->type && arg->ident)
			arg->type->toCBuffer(buf, arg->ident, &hgs);
		    else
			buf->write(namestart, namelen);
		    highlightCode(sc, s, buf, o);
		buf->writestring(")\n");

		buf->writestring("$(DDOC_PARAM_DESC ");
		    o = buf->offset;
		    buf->write(textstart, textlen);
		    highlightText(sc, s, buf, o);
		buf->writestring(")");
	    buf->writestring(")\n");
	    namelen = 0;
	    if (p >= pend)
		break;
	}

	namestart = tempstart;
	namelen = templen;

	while (*p == ' ' || *p == '\t')
	    p++;
	textstart = p;

      Ltext:
	while (*p != '\n')
	    p++;
	textlen = p - textstart;
	p++;

     Lcont:
	continue;

     Lskipline:
	// Ignore this line
	while (*p++ != '\n')
	    ;
    }
    if (namelen)
	goto L1;		// write out last one
    buf->writestring(")\n");
}

/***************************************************
 */

void MacroSection::write(DocComment *dc, Scope *sc, P_Dsymbol *s, OutBuffer *buf)
{
    //printf("MacroSection::write()\n");
    DocComment::parseMacros(dc->pescapetable, dc->pmacrotable, body, bodylen);
}

/************************************************
 * Parse macros out of Macros: section.
 * Macros are of the form:
 *	name1 = value1
 *
 *	name2 = value2
 */

void DocComment::parseMacros(Escape **pescapetable, Macro **pmacrotable, unsigned char *m, unsigned mlen)
{
    unsigned char *p = m;
    unsigned len = mlen;
    unsigned char *pend = p + len;

    unsigned char *tempstart;
    unsigned templen;

    unsigned char *namestart;
    unsigned namelen = 0;	// !=0 if line continuation

    unsigned char *textstart;
    unsigned textlen;

    while (p < pend)
    {
	// Skip to start of macro
	for (; 1; p++)
	{
	    if (p >= pend)
		goto Ldone;
	    switch (*p)
	    {
		case ' ':
		case '\t':
		    continue;

		case '\n':
		    p++;
		    goto Lcont;

		default:
		    if (!(isalpha(*p) || *p == '_'))
		    {
			if (namelen)
			    goto Ltext;		// continuation of prev macro
			goto Lskipline;
		    }
		    break;
	    }
	    break;
	}
	tempstart = p;

	while (1)
	{
	    if (p >= pend)
		goto Ldone;
	    if (!(isalnum(*p) || *p == '_'))
		break;
	    p++;
	}
	templen = p - tempstart;

	while (1)
	{
	    if (p >= pend)
		goto Ldone;
	    if (!(*p == ' ' || *p == '\t'))
		break;
	    p++;
	}

	if (*p != '=')
	{   if (namelen)
		goto Ltext;		// continuation of prev macro
	    goto Lskipline;
	}
	p++;
	if (p >= pend)
	    goto Ldone;

	if (namelen)
	{   // Output existing macro
	L1:
	    //printf("macro '%.*s' = '%.*s'\n", namelen, namestart, textlen, textstart);
	    if (icmp("ESCAPES", namestart, namelen) == 0)
		parseEscapes(pescapetable, textstart, textlen);
	    else
		Macro::define(pmacrotable, namestart, namelen, textstart, textlen);
	    namelen = 0;
	    if (p >= pend)
		break;
	}

	namestart = tempstart;
	namelen = templen;

	while (p < pend && (*p == ' ' || *p == '\t'))
	    p++;
	textstart = p;

      Ltext:
	while (p < pend && *p != '\n')
	    p++;
	textlen = p - textstart;

	// Remove trailing \r if there is one
	if (p > m && p[-1] == '\r')
	    textlen--;

	p++;
	//printf("p = %p, pend = %p\n", p, pend);

     Lcont:
	continue;

     Lskipline:
	// Ignore this line
	while (p < pend && *p++ != '\n')
	    ;
    }
Ldone:
    if (namelen)
	goto L1;		// write out last one
}

/**************************************
 * Parse escapes of the form:
 *	/c/string/
 * where c is a single character.
 * Multiple escapes can be separated
 * by whitespace and/or commas.
 */

void DocComment::parseEscapes(Escape **pescapetable, unsigned char *textstart, unsigned textlen)
{   Escape *escapetable = *pescapetable;

    if (!escapetable)
    {	escapetable = new Escape;
	*pescapetable = escapetable;
    }
    unsigned char *p = textstart;
    unsigned char *pend = p + textlen;

    while (1)
    {
	while (1)
	{
	    if (p + 4 >= pend)
		return;
	    if (!(*p == ' ' || *p == '\t' || *p == '\n' || *p == ','))
		break;
	    p++;
	}
	if (p[0] != '/' || p[2] != '/')
	    return;
	unsigned char c = p[1];
	p += 3;
	unsigned char *start = p;
	while (1)
	{
	    if (p >= pend)
		return;
	    if (*p == '/')
		break;
	    p++;
	}
	size_t len = p - start;
	char *s = (char *)memcpy(mem.malloc(len + 1), start, len);
	s[len] = 0;
	escapetable->strings[c] = s;
	//printf("%c = '%s'\n", c, s);
	p++;
    }
}


/******************************************
 * Compare 0-terminated string with length terminated string.
 * Return < 0, ==0, > 0
 */

int cmp(const char *stringz, void *s, size_t slen)
{
    size_t len1 = strlen(stringz);

    if (len1 != slen)
	return len1 - slen;
    return memcmp(stringz, s, slen);
}

int icmp(const char *stringz, void *s, size_t slen)
{
    size_t len1 = strlen(stringz);

    if (len1 != slen)
	return len1 - slen;
    return memicmp(stringz, (char *)s, slen);
}

/*****************************************
 * Return !=0 if comment consists entirely of "ditto".
 */

int isDitto(unsigned char *comment)
{
    if (comment)
    {
	unsigned char *p = skipwhitespace(comment);

	if (memicmp((char *)p, "ditto", 5) == 0 && *skipwhitespace(p + 5) == 0)
	    return 1;
    }
    return 0;
}

/**********************************************
 * Skip white space.
 */

unsigned char *skipwhitespace(unsigned char *p)
{
    for (; 1; p++)
    {	switch (*p)
	{
	    case ' ':
	    case '\t':
	    case '\n':
		continue;
	}
	break;
    }
    return p;
}


/************************************************
 * Scan forward to one of:
 *	start of identifier
 *	beginning of next line
 *	end of buf
 */

unsigned skiptoident(OutBuffer *buf, unsigned i)
{
    for (; i < buf->offset; i++)
    {
	// BUG: handle unicode alpha's
	unsigned char c = buf->data[i];
	if (isalpha(c) || c == '_')
	    break;
	if (c == '\n')
	    break;
    }
    return i;
}

/************************************************
 * Scan forward past end of identifier.
 */

unsigned skippastident(OutBuffer *buf, unsigned i)
{
    for (; i < buf->offset; i++)
    {
	// BUG: handle unicode alpha's
	unsigned char c = buf->data[i];
	if (!(isalnum(c) || c == '_'))
	    break;
    }
    return i;
}


/************************************************
 * Scan forward past URL starting at i.
 * We don't want to highlight parts of a URL.
 * Returns:
 *	i if not a URL
 *	index just past it if it is a URL
 */

unsigned skippastURL(OutBuffer *buf, unsigned i)
{   unsigned length = buf->offset - i;
    unsigned char *p = &buf->data[i];
    unsigned j;
    unsigned sawdot = 0;

    if (length > 7 && memicmp((char *)p, "http://", 7) == 0)
    {
	j = 7;
    }
    else if (length > 8 && memicmp((char *)p, "https://", 8) == 0)
    {
	j = 8;
    }
    else
	goto Lno;

    for (; j < length; j++)
    {	unsigned char c = p[j];
	if (isalnum(c))
	    continue;
        if (c == '-' || c == '_' || c == '?' ||
            c == '=' || c == '%' || c == '&' ||
            c == '/' || c == '+' || c == '#' ||
            c == '~')
            continue;
        if (c == '.')
        {
            sawdot = 1;
            continue;
        }
        break;
    }
    if (sawdot)
	return i + j;

Lno:
    return i;
}


/****************************************************
 */

int isKeyword(unsigned char *p, unsigned len)
{
    static const char *table[] = { "true", "false", "null" };

    for (int i = 0; i < sizeof(table) / sizeof(table[0]); i++)
    {
	if (cmp(table[i], p, len) == 0)
	    return 1;
    }
    return 0;
}

/****************************************************
 */

Argument *isFunctionParameter(P_Dsymbol *s, unsigned char *p, unsigned len)
{
    P_FuncDeclaration *f = s->isFuncDeclaration();

    /* f->type may be NULL for template members.
     */
    if (f && f->type)
    {
	TypeFunction *tf;
	if (f->originalType)
	{
	    tf = (TypeFunction *)f->originalType;
	}
	else
	    tf = (TypeFunction *)f->type;

	if (tf->parameters)
	{
	    for (size_t k = 0; k < tf->parameters->dim; k++)
	    {   Argument *arg = (Argument *)tf->parameters->data[k];

		if (arg->ident && cmp(arg->ident->oToChars(), p, len) == 0)
		{
		    return arg;
		}
	    }
	}
    }
    return NULL;
}

/**************************************************
 * Highlight text section.
 */

void highlightText(Scope *sc, P_Dsymbol *s, OutBuffer *buf, unsigned offset)
{
    //printf("highlightText()\n");
    const char *sid = s->ident->oToChars();
    P_FuncDeclaration *f = s->isFuncDeclaration();
    unsigned char *p;
    const char *se;

    int leadingBlank = 1;
    int inCode = 0;
    int inComment = 0;			// in <!-- ... --> comment
    unsigned iCodeStart;		// start of code section

    unsigned iLineStart = offset;

    for (unsigned i = offset; i < buf->offset; i++)
    {	unsigned char c = buf->data[i];

     Lcont:
	switch (c)
	{
	    case ' ':
	    case '\t':
		break;

	    case '\n':
		if (sc && !inCode && i == iLineStart && i + 1 < buf->offset)	// if "\n\n"
		{
		    static char blankline[] = "$(DDOC_BLANKLINE)\n";

		    i = buf->insert(i, blankline, sizeof(blankline) - 1);
		}
		leadingBlank = 1;
		iLineStart = i + 1;
		break;

	    case '<':
		leadingBlank = 0;
		if (inCode)
		    break;
		p = &buf->data[i];

		// Skip over comments
		if (p[1] == '!' && p[2] == '-' && p[3] == '-')
		{   unsigned j = i + 4;
		    p += 4;
		    while (1)
		    {
			if (j == buf->offset)
			    goto L1;
			if (p[0] == '-' && p[1] == '-' && p[2] == '>')
			{
			    i = j + 2;	// place on closing '>'
			    break;
			}
			j++;
			p++;
		    }
		    break;
		}

		// Skip over HTML tag
		if (isalpha(p[1]) || (p[1] == '/' && isalpha(p[2])))
		{   unsigned j = i + 2;
		    p += 2;
		    while (1)
		    {
			if (j == buf->offset)
			    goto L1;
			if (p[0] == '>')
			{
			    i = j;	// place on closing '>'
			    break;
			}
			j++;
			p++;
		    }
		    break;
		}

	    L1:
		// Replace '<' with '&lt;' character entity
		se = Escape::escapeChar('<');
		if (se)
		{   size_t len = strlen(se);
		    buf->remove(i, 1);
		    i = buf->insert(i, se, len);
		    i--;	// point to ';'
		}
		break;

	    case '>':
		leadingBlank = 0;
		if (inCode)
		    break;
		// Replace '>' with '&gt;' character entity
		se = Escape::escapeChar('>');
		if (se)
		{   size_t len = strlen(se);
		    buf->remove(i, 1);
		    i = buf->insert(i, se, len);
		    i--;	// point to ';'
		}
		break;

	    case '&':
		leadingBlank = 0;
		if (inCode)
		    break;
		p = &buf->data[i];
		if (p[1] == '#' || isalpha(p[1]))
		    break;			// already a character entity
		// Replace '&' with '&amp;' character entity
		se = Escape::escapeChar('&');
		if (se)
		{   size_t len = strlen(se);
		    buf->remove(i, 1);
		    i = buf->insert(i, se, len);
		    i--;	// point to ';'
		}
		break;

	    case '-':
		/* A line beginning with --- delimits a code section.
		 * inCode tells us if it is start or end of a code section.
		 */
		if (leadingBlank)
		{   int istart = i;
		    int eollen = 0;

		    leadingBlank = 0;
		    while (1)
		    {
			++i;
			if (i >= buf->offset)
			    break;
			c = buf->data[i];
			if (c == '\n')
			{   eollen = 1;
			    break;
			}
			if (c == '\r')
			{
			    eollen = 1;
			    if (i + 1 >= buf->offset)
				break;
			    if (buf->data[i + 1] == '\n')
			    {	eollen = 2;
				break;
			    }
			}
			// BUG: handle UTF PS and LS too
			if (c != '-')
			    goto Lcont;
		    }
		    if (i - istart < 3)
			goto Lcont;

		    // We have the start/end of a code section

		    // Remove the entire --- line, including blanks and \n
		    buf->remove(iLineStart, i - iLineStart + eollen);
		    i = iLineStart;

		    if (inCode)
		    {
			inCode = 0;
			// The code section is from iCodeStart to i
			OutBuffer codebuf;

			codebuf.write(buf->data + iCodeStart, i - iCodeStart);
			codebuf.writeByte(0);
			highlightCode2(sc, s, &codebuf, 0);
			buf->remove(iCodeStart, i - iCodeStart);
			i = buf->insert(iCodeStart, codebuf.data, codebuf.offset);
			i = buf->insert(i, ")\n", 2);
			i--;
		    }
		    else
		    {	static char pre[] = "$(D_CODE \n";

			inCode = 1;
			i = buf->insert(i, pre, sizeof(pre) - 1);
			iCodeStart = i;
			i--;		// place i on >
		    }
		}
		break;

	    default:
		leadingBlank = 0;
		if (sc && !inCode && (isalpha(c) || c == '_'))
		{   unsigned j;

		    j = skippastident(buf, i);
		    if (j > i)
		    {
			unsigned k = skippastURL(buf, i);
			if (k > i)
			{   i = k - 1;
			    break;
			}

			if (buf->data[i] == '_')	// leading '_' means no highlight
			{
			    buf->remove(i, 1);
			    i = j - 1;
			}
			else
			{
			    if (cmp(sid, buf->data + i, j - i) == 0)
			    {
				i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
				break;
			    }
			    else if (isKeyword(buf->data + i, j - i))
			    {
				i = buf->bracket(i, "$(DDOC_KEYWORD ", j, ")") - 1;
				break;
			    }
			    else
			    {
				if (f && isFunctionParameter(f, buf->data + i, j - i))
				{
				    //printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->oToChars(), i, j);
				    i = buf->bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
				    break;
				}
			    }
			    i = j - 1;
			}
		    }
		}
		break;
	}
    }
  Ldone:
    ;
}

/**************************************************
 * Highlight code for DDOC section.
 */

void highlightCode(Scope *sc, P_Dsymbol *s, OutBuffer *buf, unsigned offset)
{
    char *sid = s->ident->oToChars();
    P_FuncDeclaration *f = s->isFuncDeclaration();

    //printf("highlightCode(s = '%s', kind = %s)\n", sid, s->kind());
    for (unsigned i = offset; i < buf->offset; i++)
    {	unsigned char c = buf->data[i];
	const char *se;

	se = Escape::escapeChar(c);
	if (se)
	{
	    size_t len = strlen(se);
	    buf->remove(i, 1);
	    i = buf->insert(i, se, len);
	    i--;		// point to ';'
	}
	else if (isalpha(c) || c == '_')
	{   unsigned j;

	    j = skippastident(buf, i);
	    if (j > i)
	    {
		if (cmp(sid, buf->data + i, j - i) == 0)
		{
		    i = buf->bracket(i, "$(DDOC_PSYMBOL ", j, ")") - 1;
		    continue;
		}
		else if (f)
		{
		    if (isFunctionParameter(f, buf->data + i, j - i))
		    {
			//printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->oToChars(), i, j);
			i = buf->bracket(i, "$(DDOC_PARAM ", j, ")") - 1;
			continue;
		    }
		}
		i = j - 1;
	    }
	}
    }
}

/****************************************
 */

void highlightCode3(OutBuffer *buf, unsigned char *p, unsigned char *pend)
{
    for (; p < pend; p++)
    {	const char *s = Escape::escapeChar(*p);
	if (s)
	    buf->writestring(s);
	else
	    buf->writeByte(*p);
    }
}

/**************************************************
 * Highlight code for CODE section.
 */


void highlightCode2(Scope *sc, P_Dsymbol *s, OutBuffer *buf, unsigned offset)
{
    char *sid = s->ident->oToChars();
    P_FuncDeclaration *f = s->isFuncDeclaration();
    unsigned errorsave = global.errors;
    Lexer lex(NULL, buf->data, 0, buf->offset - 1, 0, 1);
    Token tok;
    OutBuffer res;
    unsigned char *lastp = buf->data;
    const char *highlight;

    //printf("highlightCode2('%.*s')\n", buf->offset - 1, buf->data);
    res.reserve(buf->offset);
    while (1)
    {
	lex.scan(&tok);
	highlightCode3(&res, lastp, tok.ptr);
	highlight = NULL;
	switch (tok.value)
	{
	    case LT::TOKidentifier:
		if (!sc)
		    break;
		if (cmp(sid, tok.ptr, lex.m_pChar - tok.ptr) == 0)
		{
		    highlight = "$(D_PSYMBOL ";
		    break;
		}
		else if (f)
		{
		    if (isFunctionParameter(f, tok.ptr, lex.m_pChar - tok.ptr))
		    {
			//printf("highlighting arg '%s', i = %d, j = %d\n", arg->ident->oToChars(), i, j);
			highlight = "$(D_PARAM ";
			break;
		    }
		}
		break;

	    case LT::TOKcomment:
		highlight = "$(D_COMMENT ";
		break;

	    case LT::TOKstring:
		highlight = "$(D_STRING ";
		break;

	    default:
		if (tok.isKeyword())
		    highlight = "$(D_KEYWORD ";
		break;
	}
	if (highlight)
	    res.writestring(highlight);
	highlightCode3(&res, tok.ptr, lex.m_pChar);
	if (highlight)
	    res.writeByte(')');
	if (tok.value == LT::TOKeof)
	    break;
	lastp = lex.m_pChar;
    }
    buf->setsize(offset);
    buf->write(&res);
    global.errors = errorsave;
}

/***************************************
 * Find character string to replace c with.
 */

const char *Escape::escapeChar(unsigned c)
{   const char *s;

    switch (c)
    {
	case '<':
	    s = "&lt;";
	    break;
	case '>':
	    s = "&gt;";
	    break;
	case '&':
	    s = "&amp;";
	    break;
	default:
	    s = NULL;
	    break;
    }
    return s;
}

